<?
class Motorchk {

	public $SITE = array(); //массив общих для всего сайта параметров
	public $PAGE = array(); //массив параметров конкретной страницы
	public $LOG = array(); //массив системных сообщений
	public $FILES = array(); //массив уже прочитанных файлов (чтобы не перечитывать заново)
	public $GEAR_SRC = array(); //исходники гиров
	public $GEAR_FUNC = array(); //гиры-функции
	public $CACHE = array();

	public function __construct() {
		define('FILENAME_URL_SLASH', '|');
		define('FILENAME_URL_CUT', '#');
		define('FILENAME_MAXLEN', 40);
		include('config.php');
		$this->SITE = array_merge($this->SITE, $site);
		$this->router();
	}

	public function router() {
		$this->PATH = str_replace("\\", "/", getcwd());
		$this->SUBDIR = substr($this->PATH, strlen($_SERVER['DOCUMENT_ROOT']));
		$this->PROTOCOL = isset($_SERVER['HTTPS']) ? 'https' : 'http';
		$this->HOME = $this->PROTOCOL.'://'.$_SERVER['HTTP_HOST'].$this->SUBDIR;
		$this->REQUEST_URL = $this->PROTOCOL.'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
		$url = strtolower(substr($_SERVER['REQUEST_URI'], 1));
		if($_SERVER['QUERY_STRING']) $url = substr($url, 0, -strlen($_SERVER['QUERY_STRING']) - 1);
		if(empty($url) || $url == substr($this->SUBDIR, 1).'/') $url .= 'index';
		$dirname = pathinfo($url, PATHINFO_DIRNAME);
		$dirname = ($dirname && $dirname != '.') ? $dirname.'/' : '';
		$url = $dirname.pathinfo($url, PATHINFO_FILENAME);
		$url = substr($url, strlen($this->SUBDIR));
		$this->PAGE['url'] = $url;
		$this->PAGE['file'] = $this->getFilenameByUrl($this->PAGE['url']);
		// $this->QUERY = strtolower($_SERVER['QUERY_STRING']);
		// parse_str($this->QUERY, $_GET);
		if(!is_file('pages/'.$this->PAGE['file'])) {
			$this->PAGE['file'] = '404.php';
			header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
		};
	}

	public function build() {
		ob_start();
		include('pages/'.$this->PAGE['file']);
		$this->PAGE = array_merge($this->PAGE, $page);
		$this->PAGE['content'] = ob_get_contents();
		ob_end_clean();
		if(!isset($this->PAGE['title'])) $this->PAGE['title'] = $this->PAGE['url'];
		if(!isset($this->PAGE['h1'])) $this->PAGE['h1'] = $this->PAGE['title'];
		$this->GEAR_SRC = $this->getGearSource($this->getGearList('gears')); //собираются все исходники гиров (из папки gears)
		$resp = $this->buildGear($this->GEAR_SRC[$this->PAGE['topgear']]);
		if(!isset($_GET['debug'])) $resp = preg_replace('(<%[^%]*%>)', '', $resp);
		return $resp;
	}

	public function getFilenameByUrl($url) {
		$url = str_replace('/', FILENAME_URL_SLASH, $url).'.php';
		if(strlen($url) <= FILENAME_MAXLEN) {
			return $url;
		}
		else {
			$hash = FILENAME_URL_CUT.substr(md5($url), 0, 8).FILENAME_URL_CUT;
			$half = floor((FILENAME_MAXLEN - strlen($hash)) / 2);
			return substr($url, 0, $half).$hash.substr($url, -$half);
		};
	}

	public function getFileList($path) {
		if(is_dir($path) && $dir = opendir($path)) {
			$files = array();
			while ($file = readdir($dir)) if($file != '.' && $file != '..') $files[] = $file;
			closedir($dir);
			return $files;
		} else return array();
	}

	public function getGearList($path) {
		$tree = $this->getFileList($path);
		$resp = array();
		foreach($tree as $i=>$file) {
			if(is_dir($path.'/'.$file)) $resp = array_merge($resp, $this->getGearList($path.'/'.$file));
			elseif(substr($file, strpos($file, '.php')) == '.php') array_push($resp, $path.'/'.$file);
		};
		return $resp;
	}

	public function getGearSource($files) {
		$resp = array();
		foreach($files as $file) {
			$gear = preg_split('/<!--<%\s*([^\s]*)\s*%>-->/', $this->despaceTags($this->getFileContent($file)), NULL, PREG_SPLIT_DELIM_CAPTURE);
			foreach($gear as $i => $_gear) {
				if($i == 0 or $i % 2 == 1) continue;
				//любой гир можно отключить (аналогично полному удалению), поставив в заголовке ! перед именем
				if(substr($gear[$i - 1], 0, 1) != '!') $resp[$gear[$i - 1]] = $_gear;
			};
		};
		return $resp;
	}

	public function getFileContent($path, $remove_comments=false) {
		if(!array_key_exists($path, $this->FILES)) {
			$this->FILES[$path] = file_exists($path) ? file_get_contents($path) : false;
			if($remove_comments) $this->FILES[$path] = preg_replace(array("'<!--.*-->'sU", "'\/\*.*\*\/'sU"), '', $this->FILES[$path]);
		};
		return $this->FILES[$path];
	}

	public function despaceTags($code) {
		return trim(
			preg_replace(
				array('/<\?\s+\?>/m', '/<%(.*)%>/Usme'),
				array('', "'<%'.trim(preg_replace(array('/\s+\?\s+/m', '/\s+&\s+/m', '/[\r\n]/m'), array('?', '&', ''), '\\1')).'%>'"),
				$code
			)
		);
	}

	public function args_hash($args=array()) {
		return sizeof($args) == 0 ? 'noargs' : md5(implode($args));
	}

	public function getGear($name, $args=array()) {
		$args_hash = $this->args_hash($args);
		if(isset($this->CACHE[$name][$args_hash])) return $this->CACHE[$name][$args_hash];
		//TODO дописать подстановку аргументов
		else return $this->buildGear('<%'.$name.'%>', true);
	}

	public function buildGear($html, $getting_cached=false) {
		global $_;
		preg_match_all("/(<%[^%]+%>)/", $html, $gears);
		foreach($gears[1] as $i => $gear_fullname) {
			$gear_fullname = substr($gear_fullname, 2, -2);
			$ok = preg_match("/(#*)([^\?]+)(\??)(.*)/i", $gear_fullname, $params);
			if($ok) {
				parse_str(preg_replace("/\s*=\s*/", "=", $params[4]), $args);
				$cache = $params[1];
				$name = $params[2];
				$query = $params[2].$params[3].$params[4];
				//TODO добавить работу с кэшем (# - гир кэшируется для конкретной страницы, ## - гир кэшируется для всего сайта)
				if(isset($this->GEAR_SRC[$name])) {
					$code = trim($this->GEAR_SRC[$name]);
					if(preg_match_all('/<%(site|page|args)\.(.*)%>/Ui', $code, $matches)) {
						$values = array();
						foreach($matches[1] as $j => $scope) {
							switch($scope) {
								case 'site': $values[] = $this->SITE[$matches[2][$j]]; break; //из настроек сайта
								case 'page': $values[] = $this->PAGE[$matches[2][$j]]; break; //из параметров страницы
								case 'args': $values[] = $args[$matches[2][$j]]; break; //аргумент от родительского гира
							};
						}
						$code = str_replace($matches[0], $values, $code);
					};
					if(strpos($code, '<?') !== FALSE) {
						if(!isset($this->GEAR_FUNC[$name])) {
							if(substr_count($code, '<?') == substr_count($code, '?>') + 1) $code .= '?>';
							$this->GEAR_FUNC[$name] = create_function('$args', 'global $_; ob_start();?>'.$code.'<?if (!isset($resp)) {$resp = array("code" => ob_get_contents()); ob_end_clean();}; return $resp;');
						};
						$resp = $this->GEAR_FUNC[$name]($args);
					}
					else
						$resp = array('code'=>$code);
					if(!empty($resp['code']) && strpos($resp['code'], '<%') !== FALSE)
						$resp['code'] = $this->buildGear($resp['code']);
					if(!isset($this->CACHE[$name]))
						$this->CACHE[$name] = array();
					$cached = $this->CACHE[$name][$this->args_hash($args)] = array('resp'=>$resp, 'args'=>$args);
					if($getting_cached)
						return $cached;
					$html = str_replace('<%'.$gear_fullname.'%>', $resp['code'], $html);
				}
				else {
					$this->LOG[] = 'ERROR: gear "'.$name.'" not found';
				};
			};
		};
		return $html;
	}
}
